use ::serde::Serialize;
use hmac::{Hmac, Mac};
use rocket::serde::{self, Deserialize};
use serde::de::{self, Visitor};
use sha2::Sha256;

pub type UserIdNum = u32;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct UserId(pub UserIdNum);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Username(pub String);

pub const SALT_LENGTH: usize = 32;
pub type Salt = [u8; SALT_LENGTH];
pub type PasswordHmac = Hmac<Sha256>;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
    pub is_admin: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PasswordCredential {
    pub hash: Vec<u8>,
    pub salt: Salt,
}

pub trait UserCredential {}
impl UserCredential for PasswordCredential {}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserCredentials {
    pub password: Option<PasswordCredential>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserIdentifier {
    pub id: UserId,
    pub username: Username,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    #[serde(flatten)]
    pub identifier: UserIdentifier,
    #[serde(flatten)]
    pub info: UserInfo,
    pub credentials: UserCredentials,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserData {
    #[serde(flatten)]
    pub identifier: UserIdentifier,
    #[serde(flatten)]
    pub info: UserInfo,
}

#[rocket::async_trait]
pub trait LoginSecret<C>
where
    C: UserCredential,
{
    async fn check(&'life0 self, credentials: &C) -> bool;
    fn is_valid(&self) -> bool;
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PasswordSecret(pub String);

pub async fn generate_hmac(password: &str, salt: &[u8]) -> Vec<u8> {
    let mut hmac = PasswordHmac::new_from_slice(salt).expect("Salt has invalid length");
    let password_bytes: Vec<u8> = password.bytes().collect();
    hmac.update(&password_bytes[..]);
    let result = hmac.finalize();
    result.into_bytes().to_vec()
}

#[rocket::async_trait]
impl LoginSecret<PasswordCredential> for PasswordSecret {
    async fn check(&'life0 self, credentials: &PasswordCredential) -> bool {
        let self_hash = generate_hmac(&self.0, &credentials.salt).await;
        self_hash == credentials.hash
    }

    fn is_valid(&self) -> bool {
        !self.0.is_empty()
    }
}

#[derive(Debug, Clone)]
pub enum UserLoginSecret {
    Password(PasswordSecret),
}

#[derive(Debug, Clone)]
pub struct UserLoginCredential {
    pub username: Username,
    pub secret: UserLoginSecret,
}

struct UserLoginCredentialVisitor;

impl<'de> Visitor<'de> for UserLoginCredentialVisitor {
    type Value = UserLoginCredential;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(formatter, "Invalid user login credentials")
    }

    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: de::MapAccess<'de>,
    {
        let mut username: Option<String> = None;
        let mut password: Option<PasswordSecret> = None;
        loop {
            match map.next_entry()? {
                Some((key, value)) => match key {
                    "username" => username = Some(value),
                    "password" => password = Some(PasswordSecret(value)),
                    unknown => {
                        return Err(de::Error::unknown_field(unknown, &["username", "password"]))
                    }
                },
                None => {
                    return if let Some(username) = username {
                        if let Some(password) = password {
                            Ok(UserLoginCredential {
                                username: Username(username),
                                secret: UserLoginSecret::Password(password),
                            })
                        } else {
                            Err(de::Error::custom(
                                "Missing login secrets, available: 'password'",
                            ))
                        }
                    } else {
                        Err(de::Error::missing_field("username"))
                    }
                }
            }
        }
    }
}

impl<'de> Deserialize<'de> for UserLoginCredential {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_struct(
            "UserLoginCredentials",
            &["username", "password"],
            UserLoginCredentialVisitor,
        )
    }
}
